/**
 * 版权所有(C)，上海海鼎信息工程股份有限公司，2007，所有权利保留。
 * 
 * 项目名：	rumba
 * 文件名：	DailySchedule.java
 * 模块说明：	
 * 修改历史：
 * Jun 18, 2007 - lxm - 创建。
 */
package com.hd123.rumba.runtime.backprocess;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

import com.hd123.rumba.exception.BusinessException;
import com.hd123.rumba.util.DateUtil;

/**
 * 每日后台任务调度 | 实现类
 * <p>
 * 
 * 调度公式：从{@link #startDate}到{@link #finishDate}期间，每{@link #every}天的指定时间{@ink #time}触发。
 * <p>
 * 
 * 具体说，把今天作为第一天，将其后的时间按照every指定的天数分割为若干段，每个段的第一天的time指定时间将被触发。
 * 
 * @author lxm
 * 
 */
public class DailySchedule implements ITaskSchedule {

  private Date time;
  private int every = 1;
  private Date startDate;
  private Date finishDate = null;

  public DailySchedule() {
    super();
    this.time = DateUtil.getTimePart(new Date());
    this.startDate = DateUtil.truncate(new Date(), Calendar.DATE);
  }

  /**
   * 构造函数。创建在每天指定时间触发的调度对象。
   * 
   * @param hour
   *          指定在一天中时刻的小时部分，24小时制。
   * @param minute
   *          指定在一天中时刻的分钟部分。
   * @param second
   *          指定在一天中时刻的秒部分。
   */
  public DailySchedule(int hour, int minute, int second) {
    super();
    this.time = DateUtil.getTimePart(new Date());
    this.startDate = DateUtil.truncate(new Date(), Calendar.DATE);
    this.setTime(hour, minute, second);
  }

  /** 属性：每几天。默认为1，>0。 */
  public int getEvery() {
    return every;
  }

  public void setEvery(int every) throws BusinessException {
    if (every <= 0)
      throw new BusinessException("属性every必须为大于等于1的整数。");
    this.every = every;
  }

  /** 结束日期。包括这一天。允许null，表示不指定结束日期。 */
  public Date getFinishDate() {
    return finishDate;
  }

  public void setFinishDate(Date finishDate) {
    this.finishDate = DateUtil.truncate(finishDate, Calendar.DATE);
  }

  /** 属性：开始日期。忽略时间部分。默认为今天。 */
  public Date getStartDate() {
    return startDate;
  }

  public void setStartDate(Date startDate) {
    this.startDate = DateUtil.truncate(startDate, Calendar.DATE);
  }

  /** 属性：在一天中的时间。将忽略日期部分。 */
  public Date getTime() {
    return time;
  }

  public void setTime(Date time) throws BusinessException {
    assert time != null;
    if (time == null)
      throw new BusinessException("属性time不允许为null。");
    this.time = DateUtil.getTimePart(time);
  }

  /**
   * 设置在一天中的时间。
   * 
   * @param hour
   *          指定小时部分，24小时制。
   * @param minute
   *          指定分钟部分。
   * @param second
   *          指定秒部分。
   */
  public void setTime(int hour, int minute, int second) {
    Calendar c = Calendar.getInstance();
    c.clear();
    c.set(Calendar.HOUR_OF_DAY, hour);
    c.set(Calendar.MINUTE, minute);
    c.set(Calendar.SECOND, second);
    this.time = c.getTime();
  }

  private static final SimpleDateFormat sdfTime = new SimpleDateFormat("HH:mm:ss");
  private static final SimpleDateFormat sdfDate = new SimpleDateFormat("yyyy-MM-dd");

  public String getDescription() throws BusinessException {
    StringBuffer sb = new StringBuffer();
    if (every == 1) {
      sb.append("每天的");
      sb.append(sdfTime.format(time));
      sb.append("执行一次。");
    } else {
      sb.append("从");
      sb.append(sdfDate.format(this.startDate));
      sb.append("开始");
      if (this.finishDate != null) {
        sb.append("到");
        sb.append(sdfDate.format(this.finishDate));
        sb.append("期间");
      }
      sb.append("每");
      sb.append(every);
      sb.append("天的");
      sb.append(sdfTime.format(time));
      sb.append("执行一次。");
    }
    return sb.toString();
  }

  public Date getNextTime() throws BusinessException {
    return getNextTime(new Date());
  }

  private static final long MILLISECONDS_OF_DAY = 24 * 60 * 60 * 1000;

  public Date getNextTime(Date baseTime) throws BusinessException {
    if (baseTime == null)
      baseTime = new Date();

    long nStartTime = this.startDate.getTime();
    long nBaseTime = baseTime.getTime();
    int nSecs = (int) Math.floor((nBaseTime - nStartTime)
        / ((double) this.every * MILLISECONDS_OF_DAY));
    Date nextTime = null;
    if (nStartTime > nBaseTime)
      nextTime = DateUtil.addDateAndTime(this.startDate, this.time);
    else {
      Date nextDate = DateUtil.add(this.startDate, Calendar.DATE, this.every * nSecs);
      nextTime = DateUtil.addDateAndTime(nextDate, this.time);
    }
    if (!nextTime.after(baseTime))
      nextTime = DateUtil.add(nextTime, Calendar.DATE, this.every);
    if (this.finishDate != null
        && !nextTime.before(DateUtil.add(this.finishDate, Calendar.DATE, 1)))
      nextTime = null;

    return nextTime;
  }

  /**
   * 判断指定的时间是否是某个触发时间。
   * 
   * @param aTime
   *          指定的时间。
   * @return
   */
  public boolean isValid(Date aTime) {
    if (aTime == null)
      return false;

    // 检查是否在开始与结束日期之间
    if (aTime.before(this.startDate))
      return false;
    if (this.finishDate != null && !aTime.before(DateUtil.add(this.finishDate, Calendar.DATE, 1)))
      return false;

    // 检查时间部分
    if (DateUtil.getTimePart(aTime).compareTo(this.time) != 0)
      return false;

    // 检查日期部分
    Date aDate = DateUtil.truncate(aTime, Calendar.DATE);
    long nDate = aDate.getTime();
    long nStartTime = this.startDate.getTime();
    long nDeltaDays = (nDate - nStartTime) / MILLISECONDS_OF_DAY;
    double secs = (double) nDeltaDays / this.every;
    return Math.floor(secs) == secs;
  }
}
